#include "Workspace.h"
#include <core/Tan.h>
#include <core/CoordinateTransformer.h>
#include <QStyleOption>
#include <math.h>
#include <stdexcept>
#include <iostream>
#include <gui/MainWindow/MainWindow.h>

namespace qtangram
{
Workspace::Workspace(QWidget* parent) : ContainerWidget(6., parent), dragStatus(false), selStatus(false), rotStatus(false)  {
}
Workspace::Workspace(double viewRange, QWidget *parent) : ContainerWidget(viewRange, parent), dragStatus(false), selStatus(false), rotStatus(false) {
}

void Workspace::calculateSelectionArea()
{
    if (selection.empty()) return;
    Tan* ctan = selection.first();

    QPointF pfmin = selection.first()->currentPoint;
    QPointF pfmax = pfmin;

    for (auto ti = selection.begin(); ti!=selection.end(); ++ti)
    {
        ctan=*ti;
        for (auto it = ctan->getCurrentPoly().begin(); it!=ctan->getCurrentPoly().end(); ++it)
        {
            if (pfmin.x() > (*it).x()) pfmin.setX((*it).x());
            if (pfmin.y() < (*it).y()) pfmin.setY((*it).y());

            if (pfmax.x() < (*it).x()) pfmax.setX((*it).x());
            if (pfmax.y() > (*it).y()) pfmax.setY((*it).y());
        }
    }

    selectionArea.setTopLeft(pfmin);
    selectionArea.setBottomRight(pfmax);
    return;
}

void Workspace::paintEvent(QPaintEvent *) {
    QStyleOption o;
    o.initFrom(this);
    TanPainter painter(this);
    //painter.setRenderHint(QPainter::Antialiasing);
    style()->drawPrimitive(QStyle::PE_Widget, &o, &painter, this);
    allTansCoordsTo(this);
    #ifdef __DEBUG__
    painter.setPen(Qt::transparent);
    painter.setBrush(Qt::yellow);
    painter.drawPolygon(CoordinateTransformer::worldToScreen(this, sum));
    #endif
    painter.setPen(Qt::black);
    painter.setBrush(Qt::black);
    for (auto it = patternPolygon.begin(); it != patternPolygon.end(); ++it)
	painter.drawPolygon(CoordinateTransformer::worldToScreen(this, (*it).polyCurrent));

    for(auto it = tans.begin(); it != tans.end(); ++it) painter.drawTan(*it);
    if (dragStatus)
    {
        for(auto it = selection.begin(); it != selection.end(); ++it) painter.drawTanContur(*it,dragTranslation);
    }
    painter.drawBounds(this);
    if (selStatus)
    {
        QRect sa(selStart,selEnd);
        painter.drawSelectionArea(sa);
    }

    if (!selection.empty())
    {
        QPen pen(Qt::blue);
        pen.setStyle(Qt::DashLine);
        painter.setPen(pen);
        QPoint a(CoordinateTransformer::worldToScreen(this,selectionArea.topLeft()));
        QPoint b(CoordinateTransformer::worldToScreen(this,selectionArea.bottomRight()));
        painter.drawRect(QRect(a,b));

        QPointF saCenter = (selectionArea.topLeft()+selectionArea.bottomRight())/2.0;
        a = CoordinateTransformer::worldToScreen(this,saCenter);
        painter.setPen(Qt::darkGreen);
        painter.drawLine(a.x()-5,a.y()-5,a.x()+5,a.y()+5);
        painter.drawLine(a.x()+5,a.y()-5,a.x()-5,a.y()+5);
    }
    if (rotStatus)
    {
        painter.setPen(Qt::red);
        QPointF a=(CoordinateTransformer::worldToScreen(this,rotCenter));
        painter.drawLine(a.x()-5,a.y()-5,a.x()+5,a.y()+5);
        painter.drawLine(a.x()+5,a.y()-5,a.x()-5,a.y()+5);
    }
}

void Workspace::clearPattern() {
    patternPolygon.clear();
}

void Workspace::mousePressEvent(QMouseEvent* event)
{
    QPointF wpos = CoordinateTransformer::screenToWorld(this,event->pos());
    if(event->button() == Qt::LeftButton)
    {
        Tan* s = findTanAt(wpos);
        if (s)
        {
            if (!isSelected(s))
            {
                deselectAllTans();
                selectTan(s);
            }
            calculateSelectionArea();
            selectionOrigin = wpos;
            dragStatus=true;
            lastMousePos=event->pos();
            dragTranslation.setX(0.);
            dragTranslation.setY(0.);
            repaint();
        }
        else
        {
            deselectAllTans();
            selStart=event->pos();
            selEnd=selStart;
            selStatus=true;
        }


    }
    else if(event->button() == Qt::RightButton)
    {
        //---vertical flip ------------------
        if (dragStatus)
        {
            calculateSelectionArea();
            QPointF saCenter = (selectionArea.topLeft()+selectionArea.bottomRight())/2.0;

            Tan* ctan;
            bool possible=true;
            for (auto ti = selection.begin(); ti!=selection.end(); ++ti)
            {
                (*ti)->flip=!((*ti)->flip);
                (*ti)->currentPoint.setX( (*ti)->currentPoint.x() + (saCenter.x()-(*ti)->currentPoint.x())*2.0   );
                (*ti)->recalculateCurrentPolygon();
            }
            for (auto ti = selection.begin(); ti!=selection.end(); ++ti)
            {
                ctan=*ti;
                for (auto it = tans.begin(); it!=tans.end(); ++it)
                {
                    if (!(*it)->selected)
                    {
                        if (ctan->polyCurrent.intersected((*it)->polyCurrent).count()  )
                        {
                            possible=false;
                        }
                    }
                }
            }
            if (!possible)
            {
                for (auto ti = selection.begin(); ti!=selection.end(); ++ti)
                {
                    (*ti)->flip=!((*ti)->flip);
                    (*ti)->currentPoint.setX( (*ti)->currentPoint.x() + (saCenter.x()-(*ti)->currentPoint.x())*2.0   );
                    (*ti)->recalculateCurrentPolygon();
                }
            }
            else
            {
                calculateSelectionArea();
            }
            repaint();

        }
        // ------enter rotation mode -----
        else
        {
            if(!selection.empty())
            {
                rotCenter=selectionArea.center();
                rotStatus=true;
                QPointF diff(CoordinateTransformer::screenToWorld(this,event->pos())-rotCenter);
                lastAngle=atan(diff.y()/diff.x())/M_PI*180.0;
                if(diff.x()<0) lastAngle=180.0+lastAngle; else if(diff.y()<0) lastAngle=360.0+lastAngle;
            }
        }


    }
}

bool Workspace::moveSelectedTo(QPoint pos)
{
    QPointF wpos(CoordinateTransformer::screenToWorld(this,pos));
    QPointF translation(wpos-selectionOrigin);
    QPointF vtl(-viewRange/2.0,viewRange/2.0);
    QPointF vbr(viewRange/2.0,-viewRange/2.0);
    QRectF vr(vtl,vbr);


    if ( !vr.contains(selectionArea.translated(translation))   )
    {
        QPointF tr2(translation);
        tr2.setX(0);
        if ( !vr.contains(selectionArea.translated(tr2))   )
        {
            tr2.setX(translation.x());
            tr2.setY(0);
        }
        if ( !vr.contains(selectionArea.translated(tr2))   ) { return false; }
        translation=tr2;
    }

    if (!selection.empty())
    {
        Tan* ctan; //(selection.first());

        for (auto ti = selection.begin(); ti!=selection.end(); ++ti)
        {
            ctan=*ti;
            for (auto it = tans.begin(); it!=tans.end(); ++it)
            {
                if (!(*it)->selected)
                {
                    if (ctan->polyCurrent.translated(translation).intersected((*it)->polyCurrent).count()  )
                    {
                        dragTranslation = pos-CoordinateTransformer::worldToScreen(this,selectionOrigin);
                        return false;
                    }
                }
            }
        }
        for (auto ti = selection.begin(); ti!=selection.end(); ++ti)
        {
            ctan=*ti;
            ctan->currentPoint+=translation;
            ctan->recalculateCurrentPolygon();
        }
        selectionOrigin+=translation;
        selectionArea.translate(translation);

    }
    dragTranslation = pos-CoordinateTransformer::worldToScreen(this,selectionOrigin);

    return true;
}

void Workspace::mouseMoveEvent(QMouseEvent* e)
{
    if (e->buttons() & Qt::LeftButton)
    {
        if (selStatus)
        {
            selEnd=e->pos();
            repaint();
        }
        if (dragStatus)
        {
            QPoint mv=lastMousePos;
            while (mv.x()!=e->pos().x())
            {
                if (mv.x()<e->pos().x()) mv.setX(mv.x()+1);
                else if (mv.x()>e->pos().x()) mv.setX(mv.x()-1);
                if (mv.y()<e->pos().y()) mv.setY(mv.y()+1);
                else if (mv.y()>e->pos().y()) mv.setY(mv.y()-1);
                moveSelectedTo(mv);
            }
            moveSelectedTo(e->pos());
            lastMousePos=e->pos();
            repaint();
        }
    }
    else if(e->buttons() & Qt::RightButton)
    {
        if (rotStatus)
        {
            QPointF diff(CoordinateTransformer::screenToWorld(this,e->pos())-rotCenter);
            double angle=atan(diff.y()/diff.x())/M_PI*180.0;
            if(diff.x()<0) angle=180.0+angle; else if(diff.y()<0) angle=360.0+angle;

            angle = angle-lastAngle;
            bool valid=true;

            QRectF wrld(QPointF(-viewRange/2.0,viewRange/2.0),
                        QPointF(viewRange/2.0,-viewRange/2.0)   );
            QMatrix m;
            m.rotate(angle);
            for (auto it=selection.begin(); it!=selection.end(); ++it)
            {
                Tan* ctan=*it;
                ctan->angle+=((ctan->flip)?(-angle):(angle));
                ctan->currentPoint-=rotCenter;
                ctan->currentPoint = ctan->currentPoint*m;
                ctan->currentPoint+=rotCenter;
                ctan->recalculateCurrentPolygon();
            }
            // intersection and location check
            for (auto ti=selection.begin(); ti!=selection.end(); ++ti)
            {
                for (auto it = tans.begin(); it!=tans.end(); ++it)
                {
                    if ((*it)!=(*ti))
                    {
                        if ((*ti)->polyCurrent.intersected((*it)->polyCurrent).count() ) valid=false;
                    }
                }
                for (auto it=(*ti)->polyCurrent.begin(); it!=(*ti)->polyCurrent.end(); ++it)
                {
                    if(!wrld.contains(*it)) valid=false;
                }
            }
            calculateSelectionArea();

            if(!valid) // rollback
            {
                m.reset();
                m.rotate(-angle);
                for (auto it=selection.begin(); it!=selection.end(); ++it)
                {
                    Tan* ctan=*it;
                    ctan->angle-=((ctan->flip)?(-angle):(angle));
                    ctan->currentPoint-=rotCenter;
                    ctan->currentPoint = ctan->currentPoint*m;
                    ctan->currentPoint+=rotCenter;
                    ctan->recalculateCurrentPolygon();
                }
                calculateSelectionArea();
            }



            lastAngle+=angle;

            repaint();
        }
    }
}

void Workspace::mouseReleaseEvent(QMouseEvent* e)
{
    if (e->button() == Qt::LeftButton)
    {
        if (dragStatus)
        {
            dragStatus = false;
            QPointF wpos(CoordinateTransformer::screenToWorld(this,e->pos()));
            QRectF wrld(QPointF(-viewRange/2.0,viewRange/2.0),
                        QPointF(viewRange/2.0,-viewRange/2.0)   );
            if (!wrld.contains(wpos)) emit sigRemoveSelectedTans();
        }
        if (selStatus)
        {
            selStatus=false;
            selEnd=e->pos();
            QPointF selStartW = CoordinateTransformer::screenToWorld(this,selStart);
            QPointF selEndW = CoordinateTransformer::screenToWorld(this,selEnd);
            selectTanAt(selStartW,selEndW);
            calculateSelectionArea();
        }
        repaint();
    }
    else if(e->button()==Qt::RightButton)
    {
        if (rotStatus)
        {
            rotStatus=false;
            repaint();
        }
    }
    emit sigCheckIfWon();
}
#ifdef __DEBUG__
void Workspace::onToleranceChange(double tol) {
    tolerance = tol;
    try { sum = summEnlargedPolygons(poly, tolerance); }
    catch(const std::runtime_error& e) { std::cerr << "Sum returned exception: " << e.what(); }
    repaint();
}
#endif
void Workspace::setPatternPolygon(QLinkedList<Tan> polygon) {
    #ifdef __DEBUG__
    poly = polygon; // not necessary - only for tests
    #endif
    QLinkedList<Tan*> poly;

    patternPolygon.clear();
    for (auto it = polygon.begin(); it != polygon.end(); ++it) {
        patternPolygon.insert((*it).ID, *it);
        poly.push_back(&(*it));
    }
    #ifdef __DEBUG__
    try { sum = summEnlargedPolygons(polygon, tolerance); }
    catch(const std::runtime_error& e) { std::cerr << "Sum returned exception: " << e.what(); }
    #endif
    Tan t;
    t.polyCurrent = poly.first()->polyCurrent;
    poly.pop_front();
    int size = poly.size();
    for (int i = 0; i<size; ++i) {
        Tan * closest = findClosest(t, poly);
        t.polyCurrent = t.polyCurrent.united(closest->polyCurrent);
        poly.removeOne(closest);
    }
}
}
